package il.ac.biu.cs.grossmm.api.keys;

/**
 * Privides base implementation of KeyPattern. Also provides generic
 * implementations of <tt>hashCode</tt>, <tt>equals</tt> and
 * <tt>toString</tt> algorithms for key patterns in static methods.
 */
public class BaseKeyPattern implements KeyPattern {
	protected final Object value;

	protected final Class valueClass;

	protected final boolean isMask;

	private static final String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t";

	/**
	 * @param value
	 * @param valueClass
	 * @param isMask
	 */
	public BaseKeyPattern(boolean isMask, Object value, boolean asValue)
	{
		super();
		if (asValue) {
			this.value = value;
			this.valueClass = null;
		} else {
			this.value = null;
			this.valueClass = (Class) value;
		}
		this.isMask = isMask;
	}

	/**
	 * Implementation of generic algorithm to calculate a hash code for key
	 * patterns
	 * 
	 * @param pattern
	 *            the key pattern
	 * @return hash code of the pattern
	 */
	public static int hashCode(KeyPattern pattern)
	{
		int hashCode = 0;

		Object value = pattern.value();
		Class valueClass = pattern.valueClass();

		if (value != null)
			hashCode += value.hashCode();
		if (valueClass != null)
			hashCode += valueClass.hashCode();
		if (pattern.isMask())
			hashCode = Integer.MAX_VALUE - hashCode;

		int size = pattern.size();

		for (int i = 0; i < size; i++) {
			PatternEntry e = pattern.entry(i);
			hashCode += e.getAttribute().hashCode();
			hashCode += e.getPattern().hashCode();
		}

		return hashCode;
	}

	/**
	 * Compares two key patterns (the second argument is <tt>Object</tt> but
	 * the method will return <tt>false</tt> if it is not a
	 * <tt>KeyPattern</tt>)
	 * 
	 * @param p1
	 *            the key pattern
	 * @param o
	 *            an object to compare with (another key pattern usually)
	 * @return true iff the second parameter is a key pattern and both key
	 *         patterns are equal
	 */
	public static boolean equals(KeyPattern p1, Object o)
	{
		if (!(o instanceof KeyPattern))
			return false;

		if (o == p1)
			return true;

		KeyPattern p2 = (KeyPattern) o;

		if (p1.value() == null) {
			// p1 defines a class pattern or null-value pattern
			if (p1.valueClass() == null) {
				// p1 defines a null-value pattern
				if (p2.value() != null || p2.valueClass() != null
					&& !p2.value().equals(Void.class))
					return false;
			} else
			// p1 defines a class pattern
			if (!p1.valueClass().equals(p2.valueClass())
				&& !(p1.valueClass().equals(Void.class) && p2.value() == null))
				return false;
		} else if (!p1.value().equals(p2.value()))
			return false;

		if (p1.size() != p2.size() || p1.isMask() != p2.isMask())
			return false;

		int size = p1.size();

		for (int i = 0; i < size; i++) {
			PatternEntry e1 = p1.entry(i);
			Attribute a = e1.getAttribute();

			PatternEntry e2 = p2.entry(a);

			if (e2 == null)
				return false;

			if (e1.isMandatory() != e2.isMandatory())
				return false;

			if (!e1.getPattern().equals(e1.getPattern()))
				return false;
		}

		return true;
	}

	public static String toString(KeyPattern p)
	{
		return read(new StringBuffer(), p, 0).toString();
	}

	private static StringBuffer read(StringBuffer sb, KeyPattern p, int tabCount)
	{
		String prefix;
		int size = p.size();
		Class valueClass = p.valueClass();

		if (tabCount == 0) {
			prefix = "";
			if (size > 0)
				sb.append("\n");
		} else {
			if (tabCount < tabs.length())
				prefix = tabs.substring(0, tabCount);
			else
				prefix = tabs;
		}

		if (valueClass == null)
			sb.append(p.value());
		else
			sb.append('[').append(valueClass.getSimpleName()).append(']');
		
		if( p.isMask() )
			sb.append(" MASK");
		else
			sb.append(" EXACT");

		if (size > 0) {

			sb.append(" {\n");

			for (int i = 0; i < size; i++) {
				PatternEntry e = p.entry(i);

				sb.append(prefix);
				sb.append('\t');
				sb.append(e.isMandatory() ? "M " : "O ");
				sb.append(e.getAttribute());
				sb.append(" = ");

				read(sb, e.getPattern(), tabCount + 1);

				sb.append(prefix).append('\n');
			}

			sb.append(prefix).append("}\n");
		}

		return sb;
	}

	public PatternEntry entry(int i)
	{
		throw new IndexOutOfBoundsException();
	}

	public PatternEntry entry(Attribute a)
	{
		return null;
	}

	public int size()
	{
		return 0;
	}

	public KeyPattern subpattern(Attribute a)
	{
		PatternEntry e = entry(a);

		if (e == null)
			return null;

		return e.getPattern();
	}

	public boolean isMask()
	{
		return isMask;
	}

	public Object value()
	{
		return value;
	}

	public Class valueClass()
	{
		return valueClass;
	}

	@Override
	public String toString()
	{
		return toString(this);
	}

	@Override
	public int hashCode()
	{
		return hashCode(this);
	}

	@Override
	public boolean equals(Object obj)
	{
		return equals(this, obj);
	}

}
